package org.acooke.uykfd.db

import java.lang.{Double => JDouble}
import java.lang.Integer
import java.lang.System
import java.sql.Connection

import org.apache.empire.db.DBReader

import scala.collection.JavaConversions._
import scala.collection.mutable.HashMap
import scala.collection.Set

class NoSuchProperty(name: String) extends RuntimeException(name)

trait PropertyRow {

  val name: String
  val description: String
  def toString: String
  def pushValue(value: String): Boolean
  def getInt: Int
  def getDouble: Double
  def getString: String

}

trait PropertyTable
  extends Table[PropertyTable, Schema.Properties] {

  def apply(name: String, cnxn: Connection): PropertyRow
  def all(cnxn: Connection): List[PropertyRow]
  def show(cnxn: Connection)

  val DB_URL: Connection => PropertyRow
  val DB_USER: Connection => PropertyRow
  val DB_PASSWORD: Connection => PropertyRow

  val LASTFM_APIKEY: Connection => PropertyRow
  val LASTFM_MINWEIGHT: Connection => PropertyRow
  val LASTFM_MAXPOLL: Connection => PropertyRow
  val LASTFM_MAXRATE: Connection => PropertyRow

  val ARTIST_MEMORY: Connection => PropertyRow
  val ARTIST_PENALTY: Connection => PropertyRow
  val ARTIST_DEXPONENT: Connection => PropertyRow
  val TRACK_MEMORY: Connection => PropertyRow
  val PATH_IN: Connection => PropertyRow
  val PATH_OUT: Connection => PropertyRow
  val BACK_BACK: Connection => PropertyRow
  val BACK_FWD: Connection => PropertyRow
}

private class PropertyFactory
  extends SimpleTable[PropertyTable, Schema.Properties](Schema.PROPERTIES)
  with PropertyTable {

  val properties = new HashMap[String, (String, String)]

  def add(name: String, description: String, default: String) = {
    properties(name) = (description, default)
    (cnxn: Connection) => new PropertyRecord(cnxn, name, description, default)
  }

  val DB_URL = add("DB_URL", "Database URL.", "Set environment UYKFD_DB_URL")
  val DB_USER = add("DB_USER", "Database user.", "")
  val DB_PASSWORD = add("DB_PASSWORD", "Database password.", "")

  val LASTFM_APIKEY = add("LASTFM_APIKEY", "Key for Last FM API.", "Set environment UYKFD_LASTFM_APIKEY")
  val LASTFM_MINWEIGHT = add("LASTFM_MINWEIGHT", "Minimum weight for tags.", "5")
  val LASTFM_MAXPOLL = add("LASTFM_MAXPOLL", "Maximum number of calls made to Last FM for one command.", "5")
  val LASTFM_MAXRATE = add("LASTFM_MAXRATE", "Maxiumum call rate (per scond).", "1.0")

  val ARTIST_MEMORY = add("ARTIST_MEMORY", "Number of artists before repeat allowed.", "10")
  val ARTIST_PENALTY = add("ARTIST_PENALTY", "Score factor for already visited artist.", "1.0")
  val ARTIST_DEXPONENT = add("ARTIST_DEXPONENT", "Exponent used in artist distance weighting.", "0")
  val TRACK_MEMORY = add("TRACK_MEMORY", "Number of tracks before repeat allowed.", "1000")
  val PATH_IN = add("PATH_IN", "Prefix added to path on reading MP3s.", "")
  val PATH_OUT = add("PATH_OUT", "Prefix added to path on writing playlist.", "")
  val BACK_BACK = add("BACK_BACK", "Backlink degree.", "4")
  val BACK_FWD = add("BACK_FWD", "Backlink forwards degree.", "2")

  def apply(name: String, cnxn: Connection) = {
    properties.get(name) match {
      case Some((description, default)) =>  new PropertyRecord(cnxn, name, description, default)
      case None => throw new NoSuchProperty(name)
    }
  }

  def all(cnxn: Connection) = {
    for (name <- properties.keySet.toList.sortWith(_ < _)) yield apply(name, cnxn)
  }

  def show(cnxn: Connection) = {
    for (property <- all(cnxn)) println(property.toString)
  }

}

private class PropertyRecord(cnxn: Connection, n: String, d: String, default: String)
  extends Record[PropertyRecord, PropertyTable, Schema.Properties](Schema.PROPERTIES)
  with PropertyRow {

  val name = n
  val description = d

  val reader = new DBReader
  try {
    val cmd = Schema.createCommand
    cmd.select(Schema.PROPERTIES.getColumns)
    cmd.where(Schema.PROPERTIES.NAME.is(name))
    reader.getRecordData(cmd, cnxn)
    initFrom(reader)
  } catch {
    case e: Exception =>
      set(Schema.PROPERTIES.NAME, name)
      pushValue(resolveDefault)
  } finally {
    reader.close
  }

  protected def resolveDefault = {
    System.getenv("UYKFD_" + name) match {
      case s: String => s
      case _ => default
    }
  } 

  override def toString = name + " = " + getString + "\n   " + description

  override def pushValue(value: String) = {
    if (cnxn == null) {
      set(Schema.PROPERTIES.VALUE, value)
      false
    } else {
      push(Schema.PROPERTIES.VALUE, value, cnxn)
      true
    }
  }

  override def getInt = Integer.parseInt(getString)
  override def getDouble = JDouble.parseDouble(getString)
  override def getString = asString(Schema.PROPERTIES.VALUE)

}

object Properties {

  def props = Schema.PROPERTIES.orm
  
  implicit def propsAsInt(property: PropertyRow): Int = property.getInt
  implicit def propsAsDouble(property: PropertyRow): Double = property.getDouble
  implicit def propsAsString(property: PropertyRow): String = property.getString
}
